﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using GDFlacTool.Common;
using Nuclex.Audio.Metadata;
using Nuclex.Networking;
using Nuclex.Support;
using Nuclex.Support.Tracking;

namespace GDFlacTool.Forms
{
    public partial class FormCDDB : Form
    {
        #region Fields.

        private readonly IList<Track> _tracks;
        private int[] _trackOffset;
        private int _discLength;
        private Cddb.Disc[] _discs;
        private CddbConnection _connection;
        private Cddb.DatabaseEntry _entry;
        private int _lastID;
        private Image _getImage;
        private byte[] _getImageByteArray;
        private readonly string _currentPath = string.Empty;
        private readonly string _artPath = string.Empty;

        #endregion

        #region Constructors.

        public FormCDDB(IList<Track> tracks, string currentPath)
        {
            InitializeComponent();
            _tracks = tracks;
            _currentPath = currentPath;
            _artPath = Path.Combine(_currentPath, "Folder.JPG");
            picCoverArt.AllowDrop = true;
        }

        #endregion

        #region Properties.

        public Image GetImage
        {
            get { return _getImage; }
        }

        public byte[] GetImageByteArray
        {
            get { return _getImageByteArray; }
        }

        #endregion

        #region Events.

        private void FormCDDB_Load(object sender, EventArgs e)
        {
            int counter = 1;
            _trackOffset = GetTrackOffSet(out _discLength);

            foreach (var offset in _trackOffset)
            {
                lsbSongTitles.Items.Add(string.Format("{0:0#} - {1}", counter, offset));
                counter++;
            }

            lsbSongTitles.Items.Add(string.Format("Disc total time: {0}", _discLength));
        }

        private void btnGetAlbum_Click(object sender, EventArgs e)
        {
            btnGetAlbum.Enabled = false;
            lsbAlbum.Items.Clear();
            GetAlbum();
        }

        private void btnGetTitles_Click(object sender, EventArgs e)
        {
            btnGetTitles.Enabled = false;
            btnSearchForCoverArt.Enabled = true;
            btnWriteTags.Enabled = true;
            grbTitles.Text = "Song titles";
            LoadGenreCombo();

            if (lsbAlbum.SelectedIndex >= 0)
            {
                bool hasEntry;

                _entry = ReadAlbumInfo(_connection, _discs[lsbAlbum.SelectedIndex], out hasEntry);

                if(hasEntry)
                {
                    ViewDiscInfo(_entry);
                }
            }
        }

        private void btnWriteTags_Click(object sender, EventArgs e)
        {
            _connection.Quit().Join();
            _connection.Dispose();

            int trackCounter = 1;

            foreach (var track in _tracks)
            {
                if (chkArtist.Checked)
                {
                    track.Artist = txtArtist.Text;
                    track.IsArtistChanged = true;
                }

                if (chkAlbumArtist.Checked)
                {
                    track.Albumartist = txtArtist.Text;
                    track.IsAlbumArtistChanged = true;
                }

                if(chkAlbum.Checked)
                {
                    track.Album = txtAlbum.Text;
                    track.IsAlbumChanged = true;
                }

                if(chkSongTitles.Checked)
                {
                    track.Title = _entry.Tracks[trackCounter - 1].Title;
                    track.IsTitleChanged = true;
                }

                if(chkTrackNumbers.Checked)
                {
                    track.TrackNumber = string.Format("{0:0#}/{1:0#}", trackCounter, _tracks.Count);
                    track.IsTrackNumberChanged = true;
                }

                if(chkYear.Checked)
                {
                    track.Year = txtYear.Text;
                    track.IsYearChanged = true;
                }

                if(chkGenre.Checked)
                {
                    track.Genre = cboGenre.Text;
                    track.IsGenreChanged = true;
                }

                if(chkSaveCoverArtToFile.Checked)
                {
                    SaveArt(_artPath);
                }

                if(chkSaveCoverArtInFile.Checked)
                {
                    track.IsCoverArtChanged = true;
                }

                trackCounter++;
            }
        }

        private void btnSearchForCoverArt_Click(object sender, EventArgs e)
        {
            try
            {
                const string url = "http://images.google.com/images?q=";
                string searchParam = string.Format("{0} - {1}&start={2}&filter={3}", txtArtist.Text, txtAlbum.Text, 1, "true");
                System.Diagnostics.Process.Start(url + searchParam);
            }
            catch (Exception ex)
            {
                string message = string.Format("Failed to open web browser and search for cover art\n\n{0}", ex.Message);

                MessageBox.Show(this, message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void chkArtist_CheckedChanged(object sender, EventArgs e)
        {
            txtArtist.Enabled = chkArtist.Checked;
        }

        private void chkAlbum_CheckedChanged(object sender, EventArgs e)
        {
            txtAlbum.Enabled = chkAlbum.Checked;
        }

        private void chkYear_CheckedChanged(object sender, EventArgs e)
        {
            txtYear.Enabled = chkYear.Checked;
        }

        private void chkGenre_CheckedChanged(object sender, EventArgs e)
        {
            cboGenre.Enabled = chkGenre.Checked;
        }

        private void lsbAlbum_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lsbAlbum.SelectedIndex >= 0)
            {
                btnGetTitles.Enabled = true;
            }
            else
            {
                btnGetTitles.Enabled = false;
            }
        }

        private void rtbLog_TextChanged(object sender, EventArgs e)
        {
            rtbLog.ScrollToCaret();
        }

        #endregion

        #region Helper Methods.

        private int[] GetTrackOffSet(out int discLength)
        {
            discLength = 0;
            var offSet = new List<int>();

            int index = 0;

            offSet.Add(0);

            foreach (var track in _tracks)
            {
                int seconds = (int)Math.Round(TimeSpan.FromTicks(track.TimeTicks).TotalSeconds, 0);

                int previous = offSet[index];

                if (index < _tracks.Count - 1)
                {
                    offSet.Add(seconds + previous);
                }
                else
                {
                    discLength = seconds + previous;
                }

                index++;
            }

            return offSet.ToArray();
        }

        private void GetAlbum()
        {
            _connection = OpenConnection();

            if(_connection == null)
                return;

            UpgradeProtocolLevel(_connection);

            _discs = QueryAlbum(_connection, _discLength, _trackOffset);

            if (_discs.Any())
            {
                rtbLog.AppendText("Matching albums retrieved from the server\n");
                ViewAlbumResult(_discs);
            }
            else
            {
                string message = string.Format("Found no matching albums");

                MessageBox.Show(this, message, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);

                rtbLog.AppendText("202 Found no matching albums\n");
            }
        }

        private void ViewAlbumResult(IEnumerable<Cddb.Disc> discs)
        {
            foreach (var disc in discs)
            {
                lsbAlbum.Items.Add(string.Format("{0} / {1} {2} +{3:x8}", disc.Artist, disc.Title, disc.Category, disc.DiscId));
            }
        }

        private void ViewDiscInfo(Cddb.DatabaseEntry entry)
        {
            int counter = 1;

            lsbSongTitles.Items.Clear();

            foreach (var track in entry.Tracks)
            {
                lsbSongTitles.Items.Add(string.Format("{0:0#} - {1}", counter, track.Title));
                counter++;
            }

            txtArtist.Text = entry.Artist;
            txtAlbum.Text = entry.Album;
            txtYear.Text = entry.Year.ToString();

            int index = cboGenre.Items.IndexOf(entry.Genre);

            if(index >= 0)
            {
                cboGenre.SelectedIndex = index;
            }
            else
            {
                cboGenre.Text = entry.Genre;
            }
        }

        private void LoadGenreCombo()
        {
            string[] genres = Genres.Audio;

            for (int i = 0; i < Genres.Audio.Length; i++)
            {
                cboGenre.Items.Add(genres[i]);
            }
        }

        #endregion

        #region CDDB Methods.

        private CddbConnection OpenConnection()
        {
            try
            {
                rtbLog.AppendText("Connecting... ");

                const string user = "gdflactool";
                const string hostname = "gdflactool.theimmortals.eu";
                const string clientname = "GDFlacTool";
                string version = Application.ProductVersion;

                var credentials = new Cddb.Credentials(
                    user,
                    hostname,
                    clientname,
                    version
                );

                CddbConnection connection = Cddb.Connect(credentials).Join();

                rtbLog.AppendText("Done\n");
                rtbLog.AppendText("Connected to " + connection.Hostname + "\n");
                rtbLog.AppendText("Running CDDB software version " + connection.Version + "\n");

                return connection;
            }
            catch (Exception ex)
            {
                string message = string.Format("Failed to opening connection to CDDB database\n\n{0}", ex.Message);

                MessageBox.Show(this, message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

                rtbLog.AppendText("Failed to opening connection to CDDB database\n");

                return null;
            }
        }

        private void UpgradeProtocolLevel(CddbConnection connection)
        {
            // Optimal protocol level for this application
            const int OptimalLevel = 6;

            // Retrieve the current and maximum supported protocol level
            rtbLog.AppendText("Retrieving current protocol level... ");
            CddbConnection.ServerProtocolLevel level = connection.GetProtocolLevel().Join();
            rtbLog.AppendText("Done\n");

            // Find the best level to use
            int newLevel = OptimalLevel;
            if (level.SupportedProtocolLevel.HasValue)
            {
                rtbLog.AppendText(string.Format("Maximum supported level is {0}\n", level.SupportedProtocolLevel.Value));

                // Use the highest level we can get, up to 6 (since this software has
                // not been tested with any later levels)
                newLevel = Math.Min(6, level.SupportedProtocolLevel.Value);
            }

            // Best level found, now apply it
            rtbLog.AppendText("Changing protocol level... ");
            connection.ChangeProtocolLevel(newLevel).Join();
            rtbLog.AppendText("Done\n");
        }

        private Cddb.Disc[] QueryAlbum(CddbConnection connection, int discLength, int[] offset)
        {
            try
            {
                rtbLog.AppendText("Querying data for album... ");
                Request<Cddb.Disc[]> queryRequest = connection.Query(discLength, offset);
                Cddb.Disc[] discs = queryRequest.Join();
                rtbLog.AppendText("Done\n");

                return discs;
            }
            catch (Exception ex)
            {
                string message = string.Format("Failed to querying data for album\n\n{0}", ex.Message);

                MessageBox.Show(this, message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

                rtbLog.AppendText("Failed to querying data for album\n");

                return null;
            }
        }

        private Cddb.DatabaseEntry ReadAlbumInfo(CddbConnection connection, Cddb.Disc disc, out bool hasInfo)
        {
            try
            {
                rtbLog.AppendText("Reading database entry... ");

                Cddb.DatabaseEntry entry;
                if (_lastID == disc.DiscId)
                {
                    entry = _entry;
                }
                else
                {
                    entry = connection.Read(disc.Category, disc.DiscId).Join();
                    
                    _lastID = disc.DiscId;
                }

                rtbLog.AppendText("Done\n");
                btnGetTitles.Enabled = true;
                hasInfo = true;
                return entry;
            }
            catch (Exception ex)
            {
                string message = string.Format("Failed to read album data\n\n{0}", ex.Message);

                MessageBox.Show(this, message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

                rtbLog.AppendText("Failed to read album data\n");

                hasInfo = false;
                
                return new Cddb.DatabaseEntry();                
            }
        }

        #endregion

        #region Drag And Drop.

        private void SaveArt(string path)
        {
            byte[] bytes = _getImageByteArray;

            MemoryStream ms = null;
            FileStream fs = null;

            try
            {
                ms = new MemoryStream(bytes);

                fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);

                ms.CopyTo(fs);

                fs.Flush();
            }
            catch (Exception ex)
            {
                string message = String.Format("There was a problem saving cover art to file [{0}]\n\n{1}", path, ex.Message);

                MessageBox.Show(this, message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                if (fs != null) fs.Close();
                if (ms != null) ms.Close();
            }
        }

        private void AddArt(string path)
        {
            if (File.Exists(path))
            {
                FileStream fs = null;
                var ms = new MemoryStream();
                try
                {
                    fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);

                    fs.CopyTo(ms);

                    _getImageByteArray = ms.ToArray();

                    Image image = Image.FromStream(fs);

                    _getImage = image;

                    lblFolderCoverArtInfo.Text = String.Format("Picture: {0} x {1}, {2}", image.Width, image.Height, Functions.GetFolderArtSizeFromFile(path));

                    picCoverArt.Image = image;

                    chkSaveCoverArtInFile.Visible = true;
                    chkSaveCoverArtInFile.Checked = true;
                    chkSaveCoverArtToFile.Visible = true;
                    chkSaveCoverArtToFile.Checked = true;
                }
                catch (Exception ex)
                {
                    string message = String.Format("There was a problem opening the cover art picture file [{0}]\n\n{1}", path, ex.Message);

                    MessageBox.Show(this, message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                finally
                {
                    if (fs != null) fs.Close();
                    ms.Close();
                }
            }
            else
            {
                picCoverArt.Image = null;
                lblFolderCoverArtInfo.Text = "Picture:";
                chkSaveCoverArtInFile.Visible = false;
                chkSaveCoverArtInFile.Checked = false;
                chkSaveCoverArtToFile.Visible = false;
                chkSaveCoverArtToFile.Checked = false;
            }
        }

        private void picCoverArt_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.Copy;
            }

            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        private void picCoverArt_DragDrop(object sender, DragEventArgs e)
        {
            try
            {
                if (e.Data.GetDataPresent(DataFormats.FileDrop))
                {
                    var filepath = (string[])e.Data.GetData(DataFormats.FileDrop);

                    AddArt(filepath[0]);

                    //chkArt.Checked = true;
                    //btnAddArt.Enabled = true;
                    //btnRemoveArt.Enabled = true;
                    //btnSaveArt.Enabled = true;
                }
            }
            catch (Exception ex)
            {
                string message = String.Format("There was a problem adding the cover art picture file!\n\n{0}", ex.Message);

                MessageBox.Show(this, message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        #endregion

        private void btnCancel_Click(object sender, EventArgs e)
        {
            try
            {
                _connection.Quit().Join();
                _connection.Dispose();
            }
            catch (Exception)
            {
                return;
            }
            
        }
    }
}
